﻿using System;
using System.Collections.Generic;
using System.Threading;
using CoreEx.Common.Cache;

namespace CoreEx.Common.Implementation.Cache
{
    /// <summary>
    /// Represents a dictionary of cached objects that can be retrieved by its key.
    /// </summary>
    /// <typeparam name="TKey">The type of keys in the cache.</typeparam>
    /// <typeparam name="TValue">The type of values in the cache.</typeparam>       
    public class LazyCache<TKey,TValue> : ILazyCache<TKey,TValue>
    {
        private readonly IDictionary<TKey, ILazyCachedValue<TValue>> _cache = new Dictionary<TKey, ILazyCachedValue<TValue>>();
        private readonly object _syncObject = new object();

        /// <summary>
        /// Returns the cached value by its <paramref name="key"/>.
        /// </summary>
        /// <param name="key">The key used to locate the cached object.</param>
        /// <param name="initializer">The <see cref="Func{TResult}"/> delegate that is executed if the cached object is not valid.</param>
        /// <returns><typeparamref name="TValue"/></returns>
        public TValue GetValue(TKey key, Func<TKey, TValue> initializer)
        {
            
            lock (_syncObject)
            {
                if (!_cache.ContainsKey(key))
                {
                    var lazyCache = new LazyCachedValue<TValue> { LifeSpan = LifeSpan };
                    _cache.Add(key, lazyCache);
                }
                return _cache[key].GetValue(() => initializer(key));
            }
        }

        /// <summary>
        /// Gets or sets a <see cref="TimeSpan"/> value that indicates the life span of the cached objects.
        /// </summary>
        /// <value></value>
        public TimeSpan LifeSpan { get; set;}

        /// <summary>
        /// Invalidates the cached object and causes the initializer delegate
        /// passed to the <see cref="GetValue"/> method to execute.
        /// <param name="key">The key used to locate the cached object.</param>
        /// </summary>
        /// <param name="key"></param>
        public void Invalidate(TKey key)
        {
            lock (_syncObject)
            {
                if (_cache.ContainsKey(key))
                    _cache[key].Invalidate();
            }
        }

        /// <summary>
        /// Invalidates all cached objects and causes the initializer delegate
        /// passed to the <see cref="GetValue"/> method to execute.
        /// </summary>
        public void Invalidate()
        {
            lock (_syncObject)
            {
                foreach (var cache in _cache.Values)
                {
                    cache.Invalidate();
                }
            }
        }   
    }
}
